import defaultValue from '../../Core/defaultValue.js'
import defined from '../../Core/defined.js'
import DeveloperError from '../../Core/DeveloperError.js'
import EllipsoidTerrainProvider from '../../Core/EllipsoidTerrainProvider.js'
import knockout from '../../ThirdParty/knockout.js'
import createCommand from '../createCommand.js'

/**
 * The view model for {@link BaseLayerPicker}.
 * @alias BaseLayerPickerViewModel
 * @constructor
 *
 * @param {Object} options Object with the following properties:
 * @param {Globe} options.globe The Globe to use.
 * @param {ProviderViewModel[]} [options.imageryProviderViewModels=[]] The array of ProviderViewModel instances to use for imagery.
 * @param {ProviderViewModel} [options.selectedImageryProviderViewModel] The view model for the current base imagery layer, if not supplied the first available imagery layer is used.
 * @param {ProviderViewModel[]} [options.terrainProviderViewModels=[]] The array of ProviderViewModel instances to use for terrain.
 * @param {ProviderViewModel} [options.selectedTerrainProviderViewModel] The view model for the current base terrain layer, if not supplied the first available terrain layer is used.
 *
 * @exception {DeveloperError} imageryProviderViewModels must be an array.
 * @exception {DeveloperError} terrainProviderViewModels must be an array.
 */
function BaseLayerPickerViewModel(options) {
  options = defaultValue(options, defaultValue.EMPTY_OBJECT)

  const globe = options.globe
  const imageryProviderViewModels = defaultValue(
    options.imageryProviderViewModels,
    []
  )
  const terrainProviderViewModels = defaultValue(
    options.terrainProviderViewModels,
    []
  )

  // >>includeStart('debug', pragmas.debug);
  if (!defined(globe)) {
    throw new DeveloperError('globe is required')
  }
  // >>includeEnd('debug');

  this._globe = globe

  /**
   * Gets or sets an array of ProviderViewModel instances available for imagery selection.
   * This property is observable.
   * @type {ProviderViewModel[]}
   */
  this.imageryProviderViewModels = imageryProviderViewModels.slice(0)

  /**
   * Gets or sets an array of ProviderViewModel instances available for terrain selection.
   * This property is observable.
   * @type {ProviderViewModel[]}
   */
  this.terrainProviderViewModels = terrainProviderViewModels.slice(0)

  /**
   * Gets or sets whether the imagery selection drop-down is currently visible.
   * @type {Boolean}
   * @default false
   */
  this.dropDownVisible = false

  knockout.track(this, [
    'imageryProviderViewModels',
    'terrainProviderViewModels',
    'dropDownVisible'
  ])

  const imageryObservable = knockout.getObservable(
    this,
    'imageryProviderViewModels'
  )
  const imageryProviders = knockout.pureComputed(function() {
    const providers = imageryObservable()
    const categories = {}
    let i
    for (i = 0; i < providers.length; i++) {
      const provider = providers[i]
      const category = provider.category
      if (defined(categories[category])) {
        categories[category].push(provider)
      } else {
        categories[category] = [provider]
      }
    }
    const allCategoryNames = Object.keys(categories)

    const result = []
    for (i = 0; i < allCategoryNames.length; i++) {
      const name = allCategoryNames[i]
      result.push({
        name: name,
        providers: categories[name]
      })
    }
    return result
  })
  this._imageryProviders = imageryProviders

  const terrainObservable = knockout.getObservable(
    this,
    'terrainProviderViewModels'
  )
  const terrainProviders = knockout.pureComputed(function() {
    const providers = terrainObservable()
    const categories = {}
    let i
    for (i = 0; i < providers.length; i++) {
      const provider = providers[i]
      const category = provider.category
      if (defined(categories[category])) {
        categories[category].push(provider)
      } else {
        categories[category] = [provider]
      }
    }
    const allCategoryNames = Object.keys(categories)

    const result = []
    for (i = 0; i < allCategoryNames.length; i++) {
      const name = allCategoryNames[i]
      result.push({
        name: name,
        providers: categories[name]
      })
    }
    return result
  })
  this._terrainProviders = terrainProviders

  /**
   * Gets the button tooltip.  This property is observable.
   * @type {String}
   */
  this.buttonTooltip = undefined
  knockout.defineProperty(this, 'buttonTooltip', function() {
    const selectedImagery = this.selectedImagery
    const selectedTerrain = this.selectedTerrain

    const imageryTip = defined(selectedImagery)
      ? selectedImagery.name
      : undefined
    const terrainTip = defined(selectedTerrain)
      ? selectedTerrain.name
      : undefined

    if (defined(imageryTip) && defined(terrainTip)) {
      return `${imageryTip}\n${terrainTip}`
    } else if (defined(imageryTip)) {
      return imageryTip
    }
    return terrainTip
  })

  /**
   * Gets the button background image.  This property is observable.
   * @type {String}
   */
  this.buttonImageUrl = undefined
  knockout.defineProperty(this, 'buttonImageUrl', function() {
    const selectedImagery = this.selectedImagery
    if (defined(selectedImagery)) {
      return selectedImagery.iconUrl
    }
  })

  /**
   * Gets or sets the currently selected imagery.  This property is observable.
   * @type {ProviderViewModel}
   * @default undefined
   */
  this.selectedImagery = undefined
  const selectedImageryViewModel = knockout.observable()

  this._currentImageryProviders = []
  knockout.defineProperty(this, 'selectedImagery', {
    get: function() {
      return selectedImageryViewModel()
    },
    set: function(value) {
      if (selectedImageryViewModel() === value) {
        this.dropDownVisible = false
        return
      }

      let i
      const currentImageryProviders = this._currentImageryProviders
      const currentImageryProvidersLength = currentImageryProviders.length
      const imageryLayers = this._globe.imageryLayers
      let hadExistingBaseLayer = false
      for (i = 0; i < currentImageryProvidersLength; i++) {
        const layersLength = imageryLayers.length
        for (let x = 0; x < layersLength; x++) {
          const layer = imageryLayers.get(x)
          if (layer.imageryProvider === currentImageryProviders[i]) {
            imageryLayers.remove(layer)
            hadExistingBaseLayer = true
            break
          }
        }
      }

      if (defined(value)) {
        const newProviders = value.creationCommand()
        if (Array.isArray(newProviders)) {
          const newProvidersLength = newProviders.length
          for (i = newProvidersLength - 1; i >= 0; i--) {
            imageryLayers.addImageryProvider(newProviders[i], 0)
          }
          this._currentImageryProviders = newProviders.slice(0)
        } else {
          this._currentImageryProviders = [newProviders]
          if (hadExistingBaseLayer) {
            imageryLayers.addImageryProvider(newProviders, 0)
          } else {
            const baseLayer = imageryLayers.get(0)
            if (defined(baseLayer)) {
              imageryLayers.remove(baseLayer)
            }
            imageryLayers.addImageryProvider(newProviders, 0)
          }
        }
      }
      selectedImageryViewModel(value)
      this.dropDownVisible = false
    }
  })

  /**
   * Gets or sets the currently selected terrain.  This property is observable.
   * @type {ProviderViewModel}
   * @default undefined
   */
  this.selectedTerrain = undefined
  const selectedTerrainViewModel = knockout.observable()

  knockout.defineProperty(this, 'selectedTerrain', {
    get: function() {
      return selectedTerrainViewModel()
    },
    set: function(value) {
      if (selectedTerrainViewModel() === value) {
        this.dropDownVisible = false
        return
      }

      let newProvider
      if (defined(value)) {
        newProvider = value.creationCommand()
      }

      this._globe.depthTestAgainstTerrain = !(
        newProvider instanceof EllipsoidTerrainProvider
      )
      this._globe.terrainProvider = newProvider
      selectedTerrainViewModel(value)
      this.dropDownVisible = false
    }
  })

  const that = this
  this._toggleDropDown = createCommand(function() {
    that.dropDownVisible = !that.dropDownVisible
  })

  this.selectedImagery = defaultValue(
    options.selectedImageryProviderViewModel,
    imageryProviderViewModels[0]
  )
  this.selectedTerrain = defaultValue(
    options.selectedTerrainProviderViewModel,
    terrainProviderViewModels[0]
  )
}

Object.defineProperties(BaseLayerPickerViewModel.prototype, {
  /**
   * Gets the command to toggle the visibility of the drop down.
   * @memberof BaseLayerPickerViewModel.prototype
   *
   * @type {Command}
   */
  toggleDropDown: {
    get: function() {
      return this._toggleDropDown
    }
  },

  /**
   * Gets the globe.
   * @memberof BaseLayerPickerViewModel.prototype
   *
   * @type {Globe}
   */
  globe: {
    get: function() {
      return this._globe
    }
  }
})
export default BaseLayerPickerViewModel
